package com.rabbit.config;

import com.rabbit.handler.UnifiedExceptionHandler;
import com.rabbit.recoverer.ExceptionImmediateRecoverer;
import com.rabbit.recoverer.ExceptionRejectRecoverer;
import com.rabbit.recoverer.ExceptionRepublishRecoverer;
import com.rabbit.route.ExchangeName;
import com.rabbit.route.QueueName;
import com.rabbit.route.RoutingKey;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.interceptor.RetryOperationsInterceptor;

/**
 * @author yuhuan
 * @date 2025/4/21
 */
@Configuration
public class MqConsumerConfig {

    @Autowired
    private MqConsumerProperties mqConsumerProperties;

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Bean
    public Queue userQueue() {
        // 用户同步队列。与消息生产端保持一致，为了从配置中获取用户同步队列的名称
        return new Queue(mqConsumerProperties.getSyncUserQueueName(), true);
    }

    @Bean
    public Queue roomQueue() {
        // 房间同步队列。与消息生产端保持一致，为了从配置中获取用户同步队列的名称
        return new Queue(mqConsumerProperties.getSyncRoomQueueName(), true);
    }

    @Bean
    public Queue deviceQueue() {
        // 设备同步队列。与消息生产端保持一致，为了从配置中获取用户同步队列的名称
        return new Queue(mqConsumerProperties.getSyncDeviceQueueName(), true);
    }

    @Bean
    public Queue hotelQueue() {
        // 酒店通知队列。与消息生产端保持一致，为了从配置中获取用户同步队列的名称
        return new Queue(mqConsumerProperties.getNoticeHotelQueueName());
    }

    @Bean
    public Queue adminQueue() {
        // 管理员通知队列。与消息生产端保持一致，为了从配置中获取用户同步队列的名称
        return new Queue(mqConsumerProperties.getNoticeAdminQueueName());
    }

    @Bean
    public UnifiedExceptionHandler unifiedExceptionHandler() {
        // 统一消息异常处理器，用于处理消息消费失败时的异常
        return new UnifiedExceptionHandler();
    }

    @Bean
    public Queue deadQueue() {
        // 创建一个持久化的死信队列，用于存储消费失败的消息
        return new Queue(QueueName.MQ_DEAD, true);
    }

    @Bean
    public DirectExchange deadExchange() {
        // 创建一个直接交换机，用来发送死信消息
        return new DirectExchange(ExchangeName.MQ_DEAD, true, false);
    }

    @Bean
    public Binding deadBinding(Queue deadQueue, DirectExchange deadExchange) {
        // 将死信队列用指定的路由键绑定到交换机
        return BindingBuilder.bind(deadQueue).to(deadExchange).with(RoutingKey.MQ_KEY_DEAD);
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);

        // 配置自定义恢复器，用于处理消息消费失败后的恢复策略
        factory.setAdviceChain(retryOperationsInterceptor());

        // 可选：配置其他参数
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(10);

        return factory;
    }

    /**
     * 系统默认使用拒绝入队的策略，建议结合死信恢复器{@link #exceptionRepublishRecoverer()}使用
     */
    @Bean
    public RetryOperationsInterceptor retryOperationsInterceptor() {
        // 创建一个无状态的重试拦截器
        return RetryInterceptorBuilder.stateless()
                .maxAttempts(3) // 最大重试次数
                .recoverer(exceptionRepublishRecoverer()) // 使用自定义恢复器，三选一
                .build();
    }

    @Bean
    public ExceptionRejectRecoverer exceptionRejectRecoverer() {
        // 创建一个拒绝消息且不重新入队的恢复器
        return new ExceptionRejectRecoverer();
    }

    /**
     * 不建议使用。如果消息消费失败的原因是永久性的（如数据格式错误、业务逻辑问题等），可能会导致消息无限重试
     */
    @Bean
    public ExceptionImmediateRecoverer exceptionImmediateRecoverer() {
        // 创建一个立即重新入队的恢复器
        return new ExceptionImmediateRecoverer();
    }

    @Bean
    public ExceptionRepublishRecoverer exceptionRepublishRecoverer() {
        // 创建一个将失败消息发送到死信的恢复器
        return new ExceptionRepublishRecoverer(amqpTemplate, ExchangeName.MQ_DEAD, RoutingKey.MQ_KEY_DEAD);
    }
}
